{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Customizing and Extending Enumerations"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Enumerations, although they behave a little differently than normal classes, are **still** classes."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This means there are many things we can customize about them."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Keep in mind that members of the enumerations are **instances** of the enumeration class, so we can implement methods in that class, and each member will have that method (boud to itself) available."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from enum import Enum"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Color(Enum):\n",
    "    red = 1\n",
    "    green = 2\n",
    "    blue = 3\n",
    "    \n",
    "    def purecolor(self, value):\n",
    "        return {self: value}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "({<Color.red: 1>: 100}, {<Color.blue: 3>: 200})"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Color.red.purecolor(100), Color.blue.purecolor(200)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Amongst other things, we can implement some of the \"standard\" dunder methods. For example we may wish to override the default representation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Color.red: 1>"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Color.red"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Color(Enum):\n",
    "    red = 1\n",
    "    green = 2\n",
    "    blue = 3\n",
    "    \n",
    "    def __repr__(self):\n",
    "        return f'{self.name} ({self.value})'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "red (1)"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Color.red"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Of course, we can implement other more interesting dunder methods."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For example, in standard enums, we do not have ordering defined for the members:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Number(Enum):\n",
    "    ONE = 1\n",
    "    TWO = 2\n",
    "    THREE = 3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "'>' not supported between instances of 'Number' and 'Number'\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    Number.ONE > Number.TWO\n",
    "except TypeError as ex:\n",
    "    print(ex)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But in this particular example it might make sense to actually have ordering defined. We can simply implement some of the rich comparison operators:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Number(Enum):\n",
    "    ONE = 1\n",
    "    TWO = 2\n",
    "    THREE = 3\n",
    "    \n",
    "    def __lt__(self, other):\n",
    "        return isinstance(other, Number) and self.value < other.value"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And now we have an ordering defined:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Number.ONE < Number.TWO"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Number.TWO > Number.ONE"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We could also potentially override the definition for equality (`==`):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Number(Enum):\n",
    "    ONE = 1\n",
    "    TWO = 2\n",
    "    THREE = 3\n",
    "    \n",
    "    def __lt__(self, other):\n",
    "        return isinstance(other, Number) and self.value < other.value\n",
    "    \n",
    "    def __eq__(self, other):\n",
    "        if isinstance(other, Number):\n",
    "            return self is other\n",
    "        elif isinstance(other, int):\n",
    "            return self.value == other\n",
    "        else:\n",
    "            return False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Number.ONE == Number.ONE"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Number.ONE == 1.0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Number.ONE == 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A good question to ask ourselves is whether our members are still hashable since we implemented a custom `__eq__` method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "unhashable type: 'Number'\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    hash(Number.ONE)\n",
    "except TypeError as ex:\n",
    "    print(ex)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And of course, they are not. We could remedy this by implementing our own `__hash__` method."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Going back to ordering:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Number(Enum):\n",
    "    ONE = 1\n",
    "    TWO = 2\n",
    "    THREE = 3\n",
    "    \n",
    "    def __lt__(self, other):\n",
    "        return isinstance(other, Number) and self.value < other.value"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Although we have `<` (and by reflection `>`) defined, we still do not have operators such as `<=`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "'<=' not supported between instances of 'Number' and 'Number'\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    Number.ONE <= Number.TWO\n",
    "except TypeError as ex:\n",
    "    print(ex)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We could of course define a `__le__` method, but we could also just use the `@totalordering` decorator:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "from functools import total_ordering"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "@total_ordering\n",
    "class Number(Enum):\n",
    "    ONE = 1\n",
    "    TWO = 2\n",
    "    THREE = 3\n",
    "    \n",
    "    def __lt__(self, other):\n",
    "        return isinstance(other, Number) and self.value < other.value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(True, True)"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Number.ONE <= Number.TWO, Number.ONE != Number.TWO"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A slightly more useful application of this ability to implement these special methods might be in this example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Phase(Enum):\n",
    "    READY = 'ready'\n",
    "    RUNNING = 'running'\n",
    "    FINISHED = 'finished'\n",
    "    \n",
    "    def __str__(self):\n",
    "        return self.value\n",
    "\n",
    "    def __eq__(self, other):\n",
    "        if isinstance(other, Phase):\n",
    "            return self is other\n",
    "        elif isinstance(other, str):\n",
    "            return self.value == other\n",
    "        return False\n",
    "    \n",
    "    def __lt__(self, other):\n",
    "        ordered_items = list(Phase)\n",
    "        self_order_index = ordered_items.index(self)\n",
    "        \n",
    "        if isinstance(other, Phase):\n",
    "            other_order_index = ordered_items.index(other)\n",
    "            return self_order_index < other_order_index\n",
    "        \n",
    "        if isinstance(other, str):\n",
    "            try:\n",
    "                other_member = Phase(other)\n",
    "                other_order_index = ordered_items.index(other_member)\n",
    "                return self_order_index < other_order_index\n",
    "            except ValueError:\n",
    "                # other is not a value in our enum\n",
    "                return False\n",
    "            "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Phase.READY == 'ready'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Phase.READY < Phase.RUNNING"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Phase.READY < 'running'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "One thing to watch out for, is that, by default, all members of an enumeration are **truthy** - irrespective of their value:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "class State(Enum):\n",
    "    READY = 1\n",
    "    BUSY = 0    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(True, True)"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bool(State.READY), bool(State.BUSY)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can of course override the `__bool__` method to customize this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [],
   "source": [
    "class State(Enum):\n",
    "    READY = 1\n",
    "    BUSY = 0    \n",
    "    \n",
    "    def __bool__(self):\n",
    "        return bool(self.value)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(True, False)"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bool(State.READY), bool(State.BUSY)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So we might implement this ready/not-ready flag in our application by simply testing the truthyness of the member:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "request_state = State.READY"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Launching next query\n"
     ]
    }
   ],
   "source": [
    "if request_state:\n",
    "    print('Launching next query')\n",
    "else:\n",
    "    print('Not ready for another query yet')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We could also easily implement a default associated truth value that reflects the truthyness of the member **values**:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Dummy(Enum):\n",
    "    A = 0\n",
    "    B = 1\n",
    "    C = ''\n",
    "    D = 'python'\n",
    "    \n",
    "    def __bool__(self):\n",
    "        return bool(self.value)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(False, True, False, True)"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bool(Dummy.A), bool(Dummy.B), bool(Dummy.C), bool(Dummy.D)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Extending Custom Enumerations"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can also extend (subclass) our custom enumerations - but only under certain circumstances: as long as the enumeration we are extending does not define any **members**:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Color(Enum):\n",
    "    RED = 1\n",
    "    GREEN = 2\n",
    "    BLUE = 3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Cannot extend enumerations\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    class ColorAlpha(Color):\n",
    "        ALPHA = 4\n",
    "except TypeError as ex:\n",
    "    print(ex)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But this would work:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [],
   "source": [
    "class ColorBase(Enum):\n",
    "    def hello(self):\n",
    "        return f'{str(self)} says hello!'\n",
    "    \n",
    "class Color(ColorBase):\n",
    "    RED = 'red'\n",
    "    GREEN = 'green'\n",
    "    BLUE = 'blue'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Color.RED says hello!'"
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Color.RED.hello()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This might not seem particularly useful (we cannot use subclassing to extended the members), but remember that we can add methods to our enumerations - this means we could define a base class that implements some common functionality for all our instances, and then extend this enumeration class to concrete enumerations that define the members."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's an example of where this might be useful:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [],
   "source": [
    "@total_ordering\n",
    "class OrderedEnum(Enum):\n",
    "    \"\"\"Creates an ordering based on the member values. \n",
    "    So member values have to support rich comparisons.\n",
    "    \"\"\"\n",
    "    \n",
    "    def __lt__(self, other):\n",
    "        if isinstance(other, OrderedEnum):\n",
    "            return self.value < other.value\n",
    "        return NotImplemented"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And now we can create other enumerations that will support ordering without having to retype the `__lt__` implementation, or even the decorator:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Number(OrderedEnum):\n",
    "    ONE = 1\n",
    "    TWO = 2\n",
    "    THREE = 3\n",
    "    \n",
    "class Dimension(OrderedEnum):\n",
    "    D1 = 1,\n",
    "    D2 = 1, 1\n",
    "    D3 = 1, 1, 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Number.ONE < Number.THREE"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 41,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Dimension.D1 < Dimension.D3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 42,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Number.ONE >= Number.ONE"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 43,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Dimension.D1 >= Dimension.D2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Of course we could implement other functionality in our base enum (maybe customized `__str__`, `__repr__`, `__bool__`, etc)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We'll actually come back to this when we discuss auto numbering in enums."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Example"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's a handy enumeration that's built-in to Python (handy if you work with http requests that is :-) )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [],
   "source": [
    "from http import HTTPStatus"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "enum.EnumMeta"
      ]
     },
     "execution_count": 45,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "type(HTTPStatus)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It's technically an `EnumMeta`, but that's beyond our current scope. Still, it's easy to use and you don't need to know anything about meta classes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<HTTPStatus.CONTINUE: 100>,\n",
       " <HTTPStatus.SWITCHING_PROTOCOLS: 101>,\n",
       " <HTTPStatus.PROCESSING: 102>,\n",
       " <HTTPStatus.OK: 200>,\n",
       " <HTTPStatus.CREATED: 201>,\n",
       " <HTTPStatus.ACCEPTED: 202>,\n",
       " <HTTPStatus.NON_AUTHORITATIVE_INFORMATION: 203>,\n",
       " <HTTPStatus.NO_CONTENT: 204>,\n",
       " <HTTPStatus.RESET_CONTENT: 205>,\n",
       " <HTTPStatus.PARTIAL_CONTENT: 206>]"
      ]
     },
     "execution_count": 46,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "list(HTTPStatus)[0:10]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<HTTPStatus.OK: 200>"
      ]
     },
     "execution_count": 47,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "HTTPStatus(200)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(<HTTPStatus.OK: 200>, 'OK', 200)"
      ]
     },
     "execution_count": 48,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "HTTPStatus.OK, HTTPStatus.OK.name, HTTPStatus.OK.value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<HTTPStatus.OK: 200>"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "HTTPStatus(200)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<HTTPStatus.OK: 200>"
      ]
     },
     "execution_count": 50,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "HTTPStatus['OK']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It even has a `phrase` property that provides a more readable version of the HTTP status (name):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(404, 'NOT_FOUND', 'Not Found')"
      ]
     },
     "execution_count": 51,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "HTTPStatus.NOT_FOUND.value, HTTPStatus.NOT_FOUND.name, HTTPStatus.NOT_FOUND.phrase"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we could implement similar functionality very easily - maybe for our own error codes in our application:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [],
   "source": [
    "class AppStatus(Enum):\n",
    "    OK = (0, 'No problem!')\n",
    "    FAILED = (1, 'Crap!')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<AppStatus.OK: (0, 'No problem!')>"
      ]
     },
     "execution_count": 53,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "AppStatus.OK"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(0, 'No problem!')"
      ]
     },
     "execution_count": 54,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "AppStatus.OK.value"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What we really want is to separate the code (lie `0`) from the phrase (like `No problem!`). We could do this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [],
   "source": [
    "class AppStatus(Enum):\n",
    "    OK = (0, 'No problem!')\n",
    "    FAILED = (1, 'Crap!')\n",
    "    \n",
    "    @property\n",
    "    def code(self):\n",
    "        return self.value[0]\n",
    "    \n",
    "    @property\n",
    "    def phrase(self):\n",
    "        return self.value[1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(0, 'No problem!')"
      ]
     },
     "execution_count": 56,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "AppStatus.OK.code, AppStatus.OK.phrase"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, it's close, but not quite the same as `HTTPStatus`...\n",
    "\n",
    "One major problem is that we can no longer lookup a member by just the code:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0 is not a valid AppStatus\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    AppStatus(0)\n",
    "except ValueError as ex:\n",
    "    print(ex)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We would have to do this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<AppStatus.OK: (0, 'No problem!')>"
      ]
     },
     "execution_count": 58,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "AppStatus((0, 'No problem!'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Not ideal..."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Let's dig in..."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "OK, so, we can actually fix this issue by making use of the `__new__` method (which we have not studied yet, but I did mention it). \n",
    "\n",
    "Remember that this is the method that gets called to **instantiate** the class - so it should return a new instance of the class. \n",
    "\n",
    "Furthemore we'll have it set the value property - for that `Enum` has a special class attribute we can use, called `_value_`. \n",
    "\n",
    "This is probably going to be a little confusing, but we'll circle back to this later:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [],
   "source": [
    "class AppStatus(Enum):\n",
    "    OK = (0, 'No Problem!')\n",
    "    FAILED = (1, 'Crap!')\n",
    "    \n",
    "    def __new__(cls, member_value, member_phrase):\n",
    "        # create a new instance of cls\n",
    "        member = object.__new__(cls)\n",
    "        \n",
    "        # set up instance attributes\n",
    "        member._value_ = member_value\n",
    "        member.phrase = member_phrase\n",
    "        return member"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(0, 'OK', 'No Problem!')"
      ]
     },
     "execution_count": 60,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "AppStatus.OK.value, AppStatus.OK.name, AppStatus.OK.phrase"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And now even looking up by numeric code works:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<AppStatus.OK: 0>"
      ]
     },
     "execution_count": 61,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "AppStatus(0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, we could easily break this out into a base class:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {},
   "outputs": [],
   "source": [
    "class TwoValueEnum(Enum):\n",
    "    def __new__(cls, member_value, member_phrase):\n",
    "        member = object.__new__(cls)\n",
    "        member._value_ = member_value\n",
    "        member.phrase = member_phrase\n",
    "        return member"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And then inherit this for any enumeration where we want to support a value as a `(code, phrase)` tuple:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "metadata": {},
   "outputs": [],
   "source": [
    "class AppStatus(TwoValueEnum):\n",
    "    OK = (0, 'No Problem!')\n",
    "    FAILED = (1, 'Crap!')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(<AppStatus.FAILED: 1>, 'FAILED', 1, 'Crap!')"
      ]
     },
     "execution_count": 64,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "AppStatus.FAILED, AppStatus.FAILED.name, AppStatus.FAILED.value, AppStatus.FAILED.phrase"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
